declare function postMessage(data: any): void;

self.addEventListener('message', (e) => {
    let id: string = e.data["type"];
    if (id == "lut_index") {
        postMessage(lut_index(e.data["pal"], false));
    }
    if (id == "lutquick_index") {
        postMessage(lut_index(e.data["pal"], true));
    }
    else if (id == "lut_rgb") {
        postMessage(lut_rgb(e.data["pal"]));
    }
    else {
        postMessage("unknown id:" + id);
    }
}, false);



var sqrt = Math.sqrt;
var pow = Math.pow;
var cos = Math.cos;
var atan2 = Math.atan2;
var sin = Math.sin;
var abs = Math.abs;
var exp = Math.exp;
var PI = Math.PI;
function degrees(n: number) { return n * (180 / PI); }
function radians(n: number) { return n * (PI / 180); }

function hp_f(x: number, y: number) //(7)
{
    if (x === 0 && y === 0) return 0;
    else {
        var tmphp = degrees(atan2(x, y));
        if (tmphp >= 0) return tmphp
        else return tmphp + 360;
    }
}

function dhp_f(C1: number, C2: number, h1p: number, h2p: number) //(10)
{
    if (C1 * C2 === 0) return 0;
    else if (abs(h2p - h1p) <= 180) return h2p - h1p;
    else if ((h2p - h1p) > 180) return (h2p - h1p) - 360;
    else if ((h2p - h1p) < -180) return (h2p - h1p) + 360;
    else throw (new Error());
}

function a_hp_f(C1: number, C2: number, h1p: number, h2p: number) { //(14)
    if (C1 * C2 === 0) return h1p + h2p
    else if (abs(h1p - h2p) <= 180) return (h1p + h2p) / 2.0;
    else if ((abs(h1p - h2p) > 180) && ((h1p + h2p) < 360)) return (h1p + h2p + 360) / 2.0;
    else if ((abs(h1p - h2p) > 180) && ((h1p + h2p) >= 360)) return (h1p + h2p - 360) / 2.0;
    else throw (new Error());
}
const Xn = 0.950456;
const Yn = 1.0;
const Zn = 1.088754;

class ColorLab {
    l: number = 0;
    a: number = 0;
    b: number = 0;


    FromRGB(r: number, g: number, b: number): void {
        let X = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b;
        let Y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b;
        let Z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b;
        let fX, fY, fZ;
        X /= (Xn);
        Y /= (Yn);
        Z /= (Zn);
        if (Y > 0.008856) {
            fY = Math.pow(Y, 1.0 / 3.0);

        }
        else {
            fY = 7.787 * Y + 16.0 / 116.0;
        }
        if (X > 0.008856)
            fX = Math.pow(X, 1.0 / 3.0);
        else
            fX = 7.787 * X + 16.0 / 116.0;

        if (Z > 0.008856)
            fZ = Math.pow(Z, 1.0 / 3.0);
        else
            fZ = 7.787 * Z + 16.0 / 116.0;
        this.l = (116.0 * Y - 16.0);

        this.a = (fX - fY) * 500;
        this.b = (fY - fZ) * 200;
    }
    static gamma(x: number): number {
        return x > 0.04045 ? Math.pow((x + 0.055) / 1.055, 2.4) : (x / 12.92);
    };

    static QuickDist(a: ColorLab, b: ColorLab): number {


        let L = (a.l - b.l);
        let A = (a.a - b.a);
        let B = (a.b - b.b);
        return Math.sqrt(
            L * L + A * A + B * B
        );
    }
    static ciede2000(c1: ColorLab, c2: ColorLab): number {
        /**
         * Implemented as in "The CIEDE2000 Color-Difference Formula:
         * Implementation Notes, Supplementary Test Data, and Mathematical Observations"
         * by Gaurav Sharma, Wencheng Wu and Edul N. Dalal.
         */

        // Get L,a,b values for color 1
        var L1 = c1.l;
        var a1 = c1.a;
        var b1 = c1.b;

        // Get L,a,b values for color 2
        var L2 = c2.l;
        var a2 = c2.a;
        var b2 = c2.b;

        // Weight factors
        var kL = 1;
        var kC = 1;
        var kH = 1;

        /**
         * Step 1: Calculate C1p, C2p, h1p, h2p
         */
        var C1 = sqrt(pow(a1, 2) + pow(b1, 2)) //(2)
        var C2 = sqrt(pow(a2, 2) + pow(b2, 2)) //(2)

        var a_C1_C2 = (C1 + C2) / 2.0;             //(3)

        var G = 0.5 * (1 - sqrt(pow(a_C1_C2, 7.0) /
            (pow(a_C1_C2, 7.0) + pow(25.0, 7.0)))); //(4)

        var a1p = (1.0 + G) * a1; //(5)
        var a2p = (1.0 + G) * a2; //(5)

        var C1p = sqrt(pow(a1p, 2) + pow(b1, 2)); //(6)
        var C2p = sqrt(pow(a2p, 2) + pow(b2, 2)); //(6)

        var h1p = hp_f(b1, a1p); //(7)
        var h2p = hp_f(b2, a2p); //(7)

        /**
         * Step 2: Calculate dLp, dCp, dHp
         */
        var dLp = L2 - L1; //(8)
        var dCp = C2p - C1p; //(9)

        var dhp = dhp_f(C1, C2, h1p, h2p); //(10)
        var dHp = 2 * sqrt(C1p * C2p) * sin(radians(dhp) / 2.0); //(11)

        /**
         * Step 3: Calculate CIEDE2000 Color-Difference
         */
        var a_L = (L1 + L2) / 2.0; //(12)
        var a_Cp = (C1p + C2p) / 2.0; //(13)

        var a_hp = a_hp_f(C1, C2, h1p, h2p); //(14)
        var T = 1 - 0.17 * cos(radians(a_hp - 30)) + 0.24 * cos(radians(2 * a_hp)) +
            0.32 * cos(radians(3 * a_hp + 6)) - 0.20 * cos(radians(4 * a_hp - 63)); //(15)
        var d_ro = 30 * exp(-(pow((a_hp - 275) / 25, 2))); //(16)
        var RC = sqrt((pow(a_Cp, 7.0)) / (pow(a_Cp, 7.0) + pow(25.0, 7.0)));//(17)
        var SL = 1 + ((0.015 * pow(a_L - 50, 2)) /
            sqrt(20 + pow(a_L - 50, 2.0)));//(18)
        var SC = 1 + 0.045 * a_Cp;//(19)
        var SH = 1 + 0.015 * a_Cp * T;//(20)
        var RT = -2 * RC * sin(radians(2 * d_ro));//(21)
        var dE = sqrt(pow(dLp / (SL * kL), 2) + pow(dCp / (SC * kC), 2) +
            pow(dHp / (SH * kH), 2) + RT * (dCp / (SC * kC)) *
            (dHp / (SH * kH))); //(22)
        return dE;
    }

}


function lut_index(palette: Uint8Array, quick: boolean): Uint8Array {
    var targetbuf = new Uint8Array(65536);

    var labs = [];
    for (var i = 0; i < 256; i++) {
        let clab = new ColorLab();
        clab.FromRGB(palette[i * 3 + 0] / 255, palette[i * 3 + 1] / 255, palette[i * 3 + 2] / 255);
        labs.push(clab);
    }


    for (var b = 0; b < 32; b++) {
        for (var r = 0; r < 32; r++) {
            for (var g = 0; g < 64; g++) {
                let index = (g << 10) | (r << 5) | (b);

                let clab = new ColorLab();
                clab.FromRGB(r / 31, g / 63, b / 31);
                //使用clab空间生成lut
                //这还不是最好的， 回头试验CIEDE2000

                // var c = new BitMath.Color8Bit_RGB();
                // c.r = r * 8;
                // c.g = g * 4;
                // c.b = b * 8;

                let colorindex = 1;
                let dist = Number.MAX_VALUE;
                for (var i = 1; i < 256; i++) {
                    let cpal = labs[i];
                    let _dist = quick ? ColorLab.QuickDist(clab, cpal) : ColorLab.ciede2000(clab, cpal);
                    // let cpal =   palette.GetColorRGB(i);
                    // let _dist = BitMath.Color8Bit_RGB.QuickDist(c, cpal);
                    if (_dist < dist) {
                        colorindex = i;
                        dist = _dist;
                    }
                }

                targetbuf[index] = colorindex;
            }
        }
    }
    return targetbuf;
}


function lut_rgb(palette: Uint8Array): Uint8Array {
    var targetbuf = new Uint8Array(65536 * 3);

    var labs = [];
    for (var i = 0; i < 256; i++) {
        let clab = new ColorLab();
        clab.FromRGB(palette[i * 3 + 0] / 255, palette[i * 3 + 1] / 255, palette[i * 3 + 2] / 255);
        labs.push(clab);
    }


    for (var b = 0; b < 32; b++) {
        for (var r = 0; r < 32; r++) {
            for (var g = 0; g < 64; g++) {
                let index = (g << 10) | (r << 5) | (b);

                let clab = new ColorLab();
                clab.FromRGB(r / 31, g / 63, b / 31);
                //使用clab空间生成lut
                //这还不是最好的， 回头试验CIEDE2000

                // var c = new BitMath.Color8Bit_RGB();
                // c.r = r * 8;
                // c.g = g * 4;
                // c.b = b * 8;

                let colorindex = 1;
                let dist = Number.MAX_VALUE;
                for (var i = 1; i < 256; i++) {
                    let cpal = labs[i];
                    let _dist = ColorLab.ciede2000(clab, cpal);
                    // let cpal =   palette.GetColorRGB(i);
                    // let _dist = BitMath.Color8Bit_RGB.QuickDist(c, cpal);
                    if (_dist < dist) {
                        colorindex = i;
                        dist = _dist;
                    }
                }

                targetbuf[index * 3 + 0] = palette[colorindex * 3 + 0];
                targetbuf[index * 3 + 1] = palette[colorindex * 3 + 1];
                targetbuf[index * 3 + 2] = palette[colorindex * 3 + 2];
            }
        }
    }
    return targetbuf;
}
